package com.zzzzzz.account.web;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.support.RequestContextUtils;

import com.zzzzzz.account.model.DayOptionBo;
import com.zzzzzz.account.model.EditAccountVo;
import com.zzzzzz.account.model.EditPasswordVo;
import com.zzzzzz.account.model.EditProfileVo;
import com.zzzzzz.account.model.GenderOptionBo;
import com.zzzzzz.account.model.MonthOptionBo;
import com.zzzzzz.account.model.PortraitForm;
import com.zzzzzz.account.model.ResendPasswordVo;
import com.zzzzzz.account.model.ResetPasswordVo;
import com.zzzzzz.account.model.SignupForm;
import com.zzzzzz.account.model.Ticket;
import com.zzzzzz.account.model.User;
import com.zzzzzz.account.model.YearOptionBo;
import com.zzzzzz.account.service.TicketService;
import com.zzzzzz.account.service.UserService;
import com.zzzzzz.common.util.Constants;
import com.zzzzzz.common.util.Validator;
import com.zzzzzz.core.exception.NoSuchUserException;
import com.zzzzzz.core.exception.UserPasswordException;
import com.zzzzzz.core.servlet.SessionMessages;
import com.zzzzzz.core.web.BaseController;
import com.zzzzzz.topic.service.TopicService;

@Controller
public class UserController extends BaseController {

	@Resource
	private UserService userService;

	@Resource
	private TicketService ticketService;

	@Resource
	private TopicService topicService;

	@Resource
	private MessageSource messageSource;

	private List<GenderOptionBo> genderOptions;
	
	private List<YearOptionBo> yearOptions;
	
	private List<MonthOptionBo> monthOptions;
	
	private List<DayOptionBo> dayOptions;

	@RequiresGuest
	@RequestMapping(value = "/signup", method = RequestMethod.GET)
	public String viewSignUpPage(Model model, @ModelAttribute SignupForm signupForm) {
		model.addAttribute("cur", "signup");
		return "account/signup";
	}

	@RequiresGuest
	@RequestMapping(value = "/signup", method = RequestMethod.POST)
	public String processSignUp(Model model, @ModelAttribute SignupForm signupForm, BindingResult result, HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
			result.reject( "message.error.process.request");
			return viewSignUpPage(model, signupForm);
		}
		try {
			User user = new User();
			signupForm.updatePo(user);
			
			userService.addAndSendAccountActivationEmail(user, request);

			model.addAttribute("email", signupForm.getEmail());
			
			return "account/signup_success";
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value = "/users/is_email_existing", method = RequestMethod.GET)
	public @ResponseBody String isEmailExisting(@RequestParam(value="email", required=true) String email, 
				Model model, HttpServletRequest request) throws Exception {
		boolean isEmailExisting = false;
		String result = "";
		try{
			isEmailExisting = userService.isEmailExisting(email);
		}catch(Exception e) {
			if(e instanceof NoSuchUserException) {
				isEmailExisting = false;
			}else {
				logger.error(e.getMessage(), e);
				throw e; // handle by Spring
			}
		}
		if(isEmailExisting) {
			result = "TRUE";
		}else {
			result = "FALSE";
		}
		return result;
	}
	
	@RequestMapping(value = "/account/activate/{token}", method = RequestMethod.GET)
	public String activate(Model model, @PathVariable String token, HttpServletRequest request) throws Exception {
		if(isSignedIn()) {
			SessionMessages.add(request, "msg_info_already_signed_in", messageSource.getMessage("message.info.already.signed.in", new Object[]{}, RequestContextUtils.getLocale(request)));
        	return "redirect:/";
		}
		try{
			Ticket ticket = getTicket(token, Constants.TICKET_TYPE.ACCOUNT_ACTIVATION);
			if(ticket != null) {
				if(ticket.isExpired()) {
					// token expired, guide to regain activation email
					String resendActivationEmailURL = request.getContextPath() + "/account/resend_activation_email/" + token;
					SessionMessages.add(request, "msg_alert_account_activate_token_expired", messageSource.getMessage("message.alert.account.activate.token.expired", new Object[]{resendActivationEmailURL}, RequestContextUtils.getLocale(request)));
				}else {
					// token haven't expired, activate account and redirect to login page(or login automatically)
			        String userId = ticket.getClassPK();
					userService.activate(userId);
					ticketService.delByKey(token);
			        
			        // login
					// userService.login(user.getEmail(), user.getPassword(), false);
					
					SessionMessages.add(request, "msg_success_activate_account", messageSource.getMessage("message.success.activate.account", new Object[]{}, RequestContextUtils.getLocale(request)));
					return "redirect:/login";
					// return "redirect:/";
				}
			}else {
				// token invalid
				SessionMessages.add(request, "msg_error_account_activate_token_invalid", messageSource.getMessage("message.error.account.activate.token.invalid", new Object[]{}, RequestContextUtils.getLocale(request)));
			}
			return "account/signup_guide";
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/account/resend_activation_email/{token}", method=RequestMethod.GET)
	public String processResendActivationEmail(Model model, @PathVariable String token, HttpServletRequest request) {
		try{
			Ticket ticket = getTicket(token, Constants.TICKET_TYPE.ACCOUNT_ACTIVATION);
			if(ticket != null) {
				if(ticket.isExpired()) {
					// token expired, resend activation email and redirect to login page
					String userId = ticket.getClassPK();
					userService.sendAccountActivationEmail(userId, request);
					ticketService.delByKey(token);
					SessionMessages.add(request, "msg_success_resend_activation_email", messageSource.getMessage("message.success.resend.activation.email", new Object[]{}, RequestContextUtils.getLocale(request)));
				}
			}
			return "redirect:/login";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			return "account/error";
		}
	}
	
	@RequestMapping(value = "/terms_of_service", method = RequestMethod.GET)
	public String viewTermsOfServicePage(Model model) {
		return "account/terms_of_service";
	}

	@RequiresGuest
	@RequestMapping(value = "/account/resend_password", method = RequestMethod.GET)
	public String viewForgotPasswordPage(Model model, @ModelAttribute ResendPasswordVo resendPasswordVo) {
		return "account/resend_password";
	}

	@RequiresGuest
	@RequestMapping(value = "/account/resend_password", method = RequestMethod.POST)
	public String processResendPassword(Model model, @ModelAttribute ResendPasswordVo resendPasswordVo, BindingResult result, HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
    		result.reject("message.error.process.request");
			return viewForgotPasswordPage(model, resendPasswordVo);
		}

		String email = resendPasswordVo.getEmail();
		
		try{
			// send reset password email
			userService.sendResetPasswordEmail(email, request);
	        return "account/password_reset_sent";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/account/reset_password/{token}", method=RequestMethod.GET)
	public String viewResetPasswordPage(Model model, @ModelAttribute ResetPasswordVo resetPasswordVo, 
			@PathVariable String token, HttpServletRequest request) {
		if(isSignedIn()) {
			SessionMessages.add(request, "msg_info_already_signed_in", messageSource.getMessage("message.info.already.signed.in", new Object[]{}, RequestContextUtils.getLocale(request)));
        	return "redirect:/";
		}else {
			model.addAttribute("token", token);
			return "account/reset_password";
		}
	}
	
    @RequestMapping(value="/account/reset_password/{token}", method= RequestMethod.POST)
    public String processResetPassword(Model model, @ModelAttribute ResetPasswordVo resetPasswordVo, BindingResult result, 
    		@PathVariable String token, HttpServletRequest request) throws Exception {
        if(result.hasErrors()) {
    		result.reject("message.error.process.request");
            return viewResetPasswordPage(model, resetPasswordVo, token, request);
        }

        try {
			Ticket ticket = getTicket(token, Constants.TICKET_TYPE.PASSWORD_RESET);
			
			if(ticket == null) {
	    		result.reject("message.error.reset.password.token.invalid");
	            return viewResetPasswordPage(model, resetPasswordVo, token, request);
			}else if(ticket.isExpired()) {
				SessionMessages.add(request, "msg_alert_pwd_reset_token_expired", messageSource.getMessage("message.alert.password.reset.token.expired", new Object[]{}, RequestContextUtils.getLocale(request)));
	            return viewResetPasswordPage(model, resetPasswordVo, token, request);
			}
			
	        String userId = ticket.getClassPK();
			userService.resetPassword(userId, resetPasswordVo.getNewPassword(), resetPasswordVo.getReNewPassword());
			ticketService.delByKey(token);
	        
	        // login
			// userManager.login(user.getEmail(), user.getPassword(), false);
			
			SessionMessages.add(request, "msg_success_pwd_reset", messageSource.getMessage("message.success.password.reset", new Object[]{}, RequestContextUtils.getLocale(request)));
			
    		// return "redirect:/";

    		return "redirect:/login";
	    	
		} catch (Exception e) {
			if(e instanceof UserPasswordException) {
				UserPasswordException upe = (UserPasswordException) e;
				if(upe.getType() == UserPasswordException.PASSWORD_INVALID) {
		    		result.reject("message.error.password.invalid");
				}
				if(upe.getType() == UserPasswordException.PASSWORDS_DO_NOT_MATCH) {
		    		result.reject("message.error.password.not.match");
				}
				if(upe.getType() == UserPasswordException.PASSWORD_SAME_AS_CURRENT) {
		    		result.reject("message.error.password.same.as.current");
				}
			}else {
	    		logger.error(e.getMessage(), e);
	    		throw e; // handle by Spring
			}
		}
        return viewResetPasswordPage(model, resetPasswordVo, token, request);
    }

	@RequestMapping(value="/", method=RequestMethod.GET)
	public String viewHomePage(Model model, HttpServletRequest request) throws Exception {
		if(!isSignedIn()) {
			model.addAttribute("cur", "index");
	        return "index";
		}
		
		model.addAttribute("cur", "home");
		try {
			User currentUser = userService.findCurrentUser();
			
			return "account/users/home";
			
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
    		throw e; // handle by Spring
		}
	}

	@RequestMapping(value="/profile", method=RequestMethod.GET)
	public String viewProfilePage(Model model, HttpServletRequest request) throws Exception {
		try {
			User currentUser = userService.findCurrentUser();
			String userId = currentUser.getUserId();
			return viewProfilePage(model, userId, request);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
    		throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/profile/{id}", method=RequestMethod.GET)
	public String viewProfilePage(Model model, @PathVariable String id, HttpServletRequest request) throws Exception {
		model.addAttribute("cur", "profile");
		try{
			User profileOwner = null;
			if(Validator.isNull(id)) {
				profileOwner = userService.findCurrentUser();
			}else {
				String userId = id;
				profileOwner = userService.findById(userId);
			}
			
			model.addAttribute("profileOwner", profileOwner);
		}catch(Exception e) {
			if(e instanceof NoSuchUserException) {
				return "exception/404";
			}else {
				logger.error(e.getMessage(), e);
				throw e; // handle by Spring
			}
		}
		return "account/users/profile";
	}
	

	@RequestMapping(value="/settings/profile", method=RequestMethod.GET)
	public String viewEditProfilePage(Model model, HttpServletRequest request, @ModelAttribute EditProfileVo editProfileVo) throws Exception {
		model.addAttribute("tab", "profile");
		try{
			User user = userService.findCurrentUser();
			
			editProfileVo.setFirstName(user.getFirstName());
			editProfileVo.setLastName(user.getLastName());
			editProfileVo.setUsername(user.getUsername());
			editProfileVo.setAboutMe(user.getAboutMe());
			editProfileVo.setGender(user.getGender());
			
			Date dob = user.getDob();
			if(dob != null) {
				Calendar c = Calendar.getInstance();
				c.setTime(dob);
				String day = String.valueOf(c.get(Calendar.DAY_OF_MONTH));
				String month = String.valueOf(c.get(Calendar.MONTH) + 1);
				String year = String.valueOf(c.get(Calendar.YEAR));
				
				editProfileVo.setDay(day);
				editProfileVo.setMonth(month);
				editProfileVo.setYear(year);
			}
			
			model.addAttribute("genderOptions", getGenderOptions());
			model.addAttribute("dayOptions", getDayOptions());
			model.addAttribute("monthOptions", getMonthOptions());
			model.addAttribute("yearOptions", getYearOptions());
			
			return "account/users/settings";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/settings/remove_portrait", method=RequestMethod.GET)
	public String processRemovePortrait(Model model, HttpServletRequest request,  @ModelAttribute PortraitForm portraitForm) throws Exception {
		model.addAttribute("tab", "portrait");
		try{
			userService.removePortrait();
			return "account/users/settings";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	

	@RequestMapping(value="/settings/portrait", method=RequestMethod.GET)
	public String viewEditPortraitPage(Model model, HttpServletRequest request,  @ModelAttribute PortraitForm portraitForm) throws Exception {
		model.addAttribute("tab", "portrait");
		try{
			String portraitId = userService.findPortraitIdByUserId();
			portraitForm.setPortraitId(portraitId);
			return "account/users/settings";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/settings/portrait", method=RequestMethod.POST)
	public String processEditPortrait(Model model, @ModelAttribute PortraitForm userPortraitForm, BindingResult result, HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
			result.reject("message.error.process.request");
			return viewEditPortraitPage(model, request, userPortraitForm);
		}
		try{
			userService.editPortrait(userPortraitForm);
			SessionMessages.add(request, "msg_success_portrait_settings", messageSource.getMessage("message.success.process.request", new Object[]{}, RequestContextUtils.getLocale(request)));
			return viewEditPortraitPage(model, request, userPortraitForm);
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/settings/account", method=RequestMethod.GET)
	public String viewEditAccountPage(Model model, HttpServletRequest request,  @ModelAttribute EditAccountVo editAccountVo) throws Exception {
		model.addAttribute("tab", "account");
		try{
			User user = userService.findCurrentUser();

			editAccountVo.setEmail(user.getEmail());
			editAccountVo.setUsername(user.getUsername());
			return "account/users/settings";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}

	@RequestMapping(value="/settings/password", method=RequestMethod.GET)
	public String viewEditPasswordPage(Model model, HttpServletRequest request, @ModelAttribute EditPasswordVo editPasswordVo) throws Exception {
		model.addAttribute("tab", "password");
		try{
			User user = userService.findCurrentUser();
			
			return "account/users/settings";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/settings/notifications", method=RequestMethod.GET)
	public String viewEditNotificationsPage(Model model, HttpServletRequest request) throws Exception {
		model.addAttribute("tab", "notifications");
		try{
			User user = userService.findCurrentUser();
			
			return "account/users/settings";
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/settings/profile", method=RequestMethod.POST)
	public String processEditProfile(Model model, @ModelAttribute EditProfileVo editProfileVo, BindingResult result, HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
			result.reject("message.error.process.request");
			return viewEditProfilePage(model, request, editProfileVo);
		}
		User user = null;
		try{
			user = userService.findCurrentUser();
			if(user != null) {
				editProfileVo.updatePo(user);
				userService.editProfileBasic(user);
				SessionMessages.add(request, "msg_success_profile_settings", messageSource.getMessage("message.success.process.request", new Object[]{}, RequestContextUtils.getLocale(request)));
		
			}
			return viewEditProfilePage(model, request, editProfileVo);
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}

	@RequestMapping(value="/settings/account", method=RequestMethod.POST)
	public String processEditAccount(Model model, @ModelAttribute EditAccountVo editAccountVo, BindingResult result, HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
			result.reject("message.error.process.request");
			return viewEditAccountPage(model, request, editAccountVo);
		}
		User user = null;
		try{
			user = userService.findCurrentUser();
			if(user != null) {
				editAccountVo.updatePo(user);
				try {
					userService.editAccountGeneral(user, editAccountVo.getPassword());
					SessionMessages.add(request, "msg_success_account_settings", messageSource.getMessage("message.success.process.request", new Object[]{}, RequestContextUtils.getLocale(request)));
				}catch(Exception e) {
					if(e instanceof UserPasswordException) {
						UserPasswordException upe = (UserPasswordException) e;
						if(upe.getType() == UserPasswordException.PASSWORDS_WRONG) {
				    		result.reject("message.error.password.wrong");
						}
					}else {
			    		logger.error(e.getMessage(), e);
			    		throw e; // handle by Spring
					}
				}
			}
			return viewEditAccountPage(model, request, editAccountVo);
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/settings/password", method=RequestMethod.POST)
	public String processEditPassword(Model model, @ModelAttribute EditPasswordVo editPasswordVo, BindingResult result, HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
			result.reject("message.error.process.request");
			return viewEditPasswordPage(model, request, editPasswordVo);
		}
		User user = null;
		try{
			user = userService.findCurrentUser();
			if(user != null) {
				try{
					userService.editPassword(user, editPasswordVo.getCurPassword(), editPasswordVo.getNewPassword(), editPasswordVo.getReNewPassword());
					SessionMessages.add(request, "msg_success_password_settings", messageSource.getMessage("message.success.process.request", new Object[]{}, RequestContextUtils.getLocale(request)));
				}catch(Exception e) {
					if(e instanceof UserPasswordException) {
						UserPasswordException upe = (UserPasswordException) e;
						if(upe.getType() == UserPasswordException.PASSWORD_INVALID) {
				    		result.reject("message.error.password.invalid");
						}
						if(upe.getType() == UserPasswordException.PASSWORDS_WRONG) {
				    		result.reject("message.error.password.wrong");
						}
						if(upe.getType() == UserPasswordException.PASSWORDS_DO_NOT_MATCH) {
				    		result.reject("message.error.password.not.match");
						}
						if(upe.getType() == UserPasswordException.PASSWORD_SAME_AS_CURRENT) {
				    		result.reject("message.error.password.same.as.current");
						}
					}else {
			    		logger.error(e.getMessage(), e);
			    		throw e; // handle by Spring
					}
				}
			}
			return viewEditPasswordPage(model, request, editPasswordVo);
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value="/settings/notifications", method=RequestMethod.POST)
	public String processEditNotifications(Model model, BindingResult result, HttpServletRequest request) throws Exception {
		if(result.hasErrors()) {
			result.reject("message.error.process.request");
			return viewEditNotificationsPage(model, request);
		}
		return viewEditNotificationsPage(model, request);
	}
	
	@RequiresRoles(value="administrator")
	@RequestMapping(value = "/control_panel", method = RequestMethod.GET)
	public String viewControlPanelPage(Model model, HttpServletRequest request) {
		model.addAttribute("cur", "control_panel");
		return "admin/control_panel";
	}

	@RequiresRoles(value="administrator")
	@RequestMapping(value = "/control_panel/db/mongo", method = RequestMethod.GET)
	public String viewMongoPage(Model model, HttpServletRequest request) throws Exception {
		model.addAttribute("tab", "mongo");
		return viewControlPanelPage(model, request);
	}

	@RequiresRoles(value="administrator")
	@RequestMapping(value = "/control_panel/db/neo4j", method = RequestMethod.GET)
	public String viewNeo4jPage(Model model, HttpServletRequest request) throws Exception {
		model.addAttribute("tab", "neo4j");
		return viewControlPanelPage(model, request);
	}
	

	private List<GenderOptionBo> getGenderOptions() {
		if(genderOptions == null) {
			genderOptions = new ArrayList<GenderOptionBo>();
			for(int i=0; i<genderArray.length; i++) {
				String genderLabel = genderArray[i];
				GenderOptionBo gender = new GenderOptionBo(genderLabel, genderLabel.toLowerCase());
				genderOptions.add(gender);
			}
		}
		return genderOptions;
	}

	private List<YearOptionBo> getYearOptions() {
        Calendar calendar = Calendar.getInstance();
        int curYear = calendar.get(Calendar.YEAR);
		if(yearOptions == null) {
			yearOptions = new ArrayList<YearOptionBo>();
	        for(int i=curYear; i>=curYear-106; i--) {
				String yearLabel = i + "";
				YearOptionBo yearOption = new YearOptionBo(yearLabel, yearLabel);
				yearOptions.add(yearOption);
	        }
		}
		return yearOptions;
	}

	private List<MonthOptionBo> getMonthOptions() {
		if(monthOptions == null) {
			monthOptions = new ArrayList<MonthOptionBo>();
			for(int i=1; i<=monthArray.length; i++) {
				String monthLabel = monthArray[i-1];
				String monthValue = i + "";
				MonthOptionBo monthOption = new MonthOptionBo(monthLabel, monthValue);
				monthOptions.add(monthOption);
			}
		}
		return monthOptions;
	}

	private List<DayOptionBo> getDayOptions() {
		if(dayOptions == null) {
			dayOptions = new ArrayList<DayOptionBo>();
			for(int i=1; i<=31; i++) {
				String dayLabel = i + "";
				DayOptionBo dayOption = new DayOptionBo(dayLabel, dayLabel);
				dayOptions.add(dayOption);
			}
		}
		return dayOptions;
	}

	private static final String[] genderArray = {"Male", "Female"};

	private static final String[] monthArray = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"};

	private static final Logger logger = LoggerFactory.getLogger(UserController.class);
	
}
